{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "d68e79e3",
   "metadata": {
    "id": "f8cj-HBNoEZy"
   },
   "source": [
    "# Week 3: Transfer Learning\n",
    "\n",
    "Welcome to this assignment! This week, you are going to use a technique called `Transfer Learning` in which you utilize an already trained network to help you solve a similar problem to the one it was originally trained to solve.\n",
    "\n",
    "#### TIPS FOR SUCCESSFUL GRADING OF YOUR ASSIGNMENT:\n",
    "\n",
    "- All cells are frozen except for the ones where you need to submit your solutions or when explicitly mentioned you can interact with it.\n",
    "\n",
    "- You can add new cells to experiment but these will be omitted by the grader, so don't rely on newly created cells to host your solution code, use the provided places for this.\n",
    "\n",
    "- ou can add the comment # grade-up-to-here in any graded cell to signal the grader that it must only evaluate up to that point. This is helpful if you want to check if you are on the right track even if you are not done with the whole assignment. Be sure to remember to delete the comment afterwards!\n",
    "\n",
    "- Avoid using global variables unless you absolutely have to. The grader tests your code in an isolated environment without running all cells from the top. As a result, global variables may be unavailable when scoring your submission. Global variables that are meant to be used will be defined in UPPERCASE.\n",
    "\n",
    "- To submit your notebook, save it and then click on the blue submit button at the beginning of the page.\n",
    "\n",
    "Let's get started!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b103aafe",
   "metadata": {
    "deletable": false,
    "editable": false,
    "id": "lbFmQdsZs5eW",
    "tags": [
     "graded"
    ]
   },
   "outputs": [],
   "source": [
    "import os\n",
    "import matplotlib.pyplot as plt\n",
    "import tensorflow as tf"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a3619bbd",
   "metadata": {
    "deletable": false,
    "editable": false,
    "tags": []
   },
   "outputs": [],
   "source": [
    "import unittests"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d0e77388",
   "metadata": {
    "id": "RPvtLK1GyUWr"
   },
   "source": [
    "## Dataset\n",
    "\n",
    "For this assignment, you will use the `Horse or Human dataset`, which contains images of horses and humans. \n",
    "\n",
    "All the images are contained within the `./data/` directory. The complete tree looks like this:\n",
    "```\n",
    ".\n",
    "└── data/\n",
    "    ├── train/\n",
    "    │   ├── horses/\n",
    "    │   │   ├── train_horse_1.png\n",
    "    │   │   └── ...\n",
    "    │   └── humans/\n",
    "    │       ├── train_human_1.png\n",
    "    │       └── ...\n",
    "    └── validation/\n",
    "        ├── horses/\n",
    "        │   ├── val_horse_1.png\n",
    "        │   └── ...\n",
    "        └── humans/\n",
    "            ├── val_human_1.png\n",
    "            └── ...\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9e08beb8-e238-4a5f-bd33-19fd5315ba37",
   "metadata": {
    "deletable": false,
    "editable": false,
    "tags": [
     "graded"
    ]
   },
   "outputs": [],
   "source": [
    "TRAIN_DIR = './data/train/'\n",
    "VALIDATION_DIR = './data/validation/'"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "525149b3",
   "metadata": {
    "id": "1G5hXBB57c78"
   },
   "source": [
    "Now take a look at a sample image of each one of the classes. You will simply be picking the first image from each class in the `train` folder."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b48c972c-f73c-49e2-8f51-304276b2f771",
   "metadata": {
    "deletable": false,
    "editable": false,
    "tags": []
   },
   "outputs": [],
   "source": [
    "# Directories for each class\n",
    "horses_dir = os.path.join(TRAIN_DIR, 'horses')\n",
    "humans_dir = os.path.join(TRAIN_DIR, 'humans')\n",
    "\n",
    "# Load the first example of each one of the classes\n",
    "sample_image_horse  = tf.keras.preprocessing.image.load_img(os.path.join(horses_dir, os.listdir(horses_dir)[0]))\n",
    "sample_image_human  = tf.keras.preprocessing.image.load_img(os.path.join(humans_dir, os.listdir(humans_dir)[0]))\n",
    "\n",
    "ax = plt.subplot(1,2,1)\n",
    "ax.imshow(sample_image_horse)\n",
    "ax.set_title('Sample horse image')\n",
    "\n",
    "ax = plt.subplot(1,2,2)\n",
    "ax.imshow(sample_image_human)\n",
    "ax.set_title('Sample human image')\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "173f0d9d",
   "metadata": {
    "id": "LBnbnY0c8Zd0"
   },
   "source": [
    "By plotting the images with `matplotlib` it is easy to see that these images have a resolution of 300x300 (look at the image axes) and are colored, but you can double check this by using the code below:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "fb51a0be",
   "metadata": {
    "deletable": false,
    "editable": false,
    "id": "4lIGjHC5pxua",
    "tags": []
   },
   "outputs": [],
   "source": [
    "# Convert the image into its numpy array representation\n",
    "sample_array = tf.keras.preprocessing.image.img_to_array(sample_image_horse)\n",
    "\n",
    "print(f\"Each image has shape: {sample_array.shape}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "142c6e53",
   "metadata": {
    "id": "4fYwAYyd8zEm"
   },
   "source": [
    "As expected, the sample image has a resolution of 300x300 and the last dimension is used for each one of the RGB channels to represent color."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c3d2f121",
   "metadata": {
    "id": "6HcE1TSqNRY2"
   },
   "source": [
    "## Exercise 1: train_val_datasets\n",
    "Now that you have a better understanding of the images you are dealing with, it is time for you to code the datsets that will feed these images to your network. For this, complete the `train_val_datasets` function below, in which you will be using the [`image_dataset_from_directory`](https://www.tensorflow.org/api_docs/python/tf/keras/utils/image_dataset_from_directory) function from `tf.keras.utils`. For grading purposes, use a batch size of 32 for the generators, you can later test what happens if you change this parameter.\n",
    "\n",
    "**Important Note:** The images have a resolution of 300x300 but the `image_dataset_from_directory` method you will use allows you to set a target resolution. In this case, **set a `image_size` of (150, 150)**. This will heavily lower the number of trainable parameters in your final network, yielding much quicker training times without compromising the accuracy! \n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "237e94d3",
   "metadata": {
    "cellView": "code",
    "deletable": false,
    "id": "AX5Q3NL_FXMT",
    "tags": [
     "graded"
    ]
   },
   "outputs": [],
   "source": [
    "# GRADED FUNCTION: train_val_datasets\n",
    "\n",
    "def train_val_datasets():\n",
    "    \"\"\"Creates training and validation datasets\n",
    "\n",
    "    Returns:\n",
    "        (tf.data.Dataset, tf.data.Dataset): training and validation datasets\n",
    "    \"\"\"\n",
    "\n",
    "    ### START CODE HERE ###\n",
    "\n",
    "    training_dataset = tf.keras.utils.image_dataset_from_directory( \n",
    "        directory=None,\n",
    "        batch_size=None,\n",
    "        image_size=None,\n",
    "        shuffle=True, \n",
    "        seed=7 \n",
    "    ) \n",
    "    \n",
    "    validation_dataset = tf.keras.utils.image_dataset_from_directory( \n",
    "        directory=None,\n",
    "        batch_size=None,\n",
    "        image_size=None,\n",
    "        shuffle=True, \n",
    "        seed=7 \n",
    "    ) \n",
    "\n",
    "    ### END CODE HERE ###\n",
    "                                                                        \n",
    "    return training_dataset, validation_dataset"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6af52ef3-9bb0-4e56-8f60-8b8fab6f396b",
   "metadata": {
    "deletable": false,
    "editable": false,
    "tags": []
   },
   "outputs": [],
   "source": [
    "# Test your generators\n",
    "training_dataset, validation_dataset = train_val_datasets()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "39b40168",
   "metadata": {
    "id": "TszKWhunQaj4"
   },
   "source": [
    "**Expected Output:**\n",
    "```\n",
    "Found 1027 images belonging to 2 classes.\n",
    "Found 256 images belonging to 2 classes.\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c7f684a9",
   "metadata": {
    "deletable": false,
    "editable": false,
    "tags": []
   },
   "outputs": [],
   "source": [
    "# Test your code!\n",
    "unittests.test_train_val_datasets(train_val_datasets)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f0b72caa-5fa1-4afd-830c-b1145635b1ae",
   "metadata": {},
   "source": [
    "Ultimately, you will want to use your trained model to predict new images, so it is always good to reserve some images for the test set. This will be images never seen by the model, which you can use to check your final model performance. As the original dataset doesn't contain a test set, you will create one by splitting the validation dataset."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e1b3afee-b0d8-4b3d-b92a-3be6ba5e56df",
   "metadata": {
    "deletable": false,
    "editable": false,
    "tags": []
   },
   "outputs": [],
   "source": [
    "val_batches = int(validation_dataset.cardinality())\n",
    "test_dataset, validation_dataset = tf.keras.utils.split_dataset(validation_dataset, val_batches//5)\n",
    "\n",
    "print(f'Number of validation batches: {validation_dataset.cardinality()}')\n",
    "print(f'Number of test batches: {test_dataset.cardinality()}')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "31a028c0",
   "metadata": {
    "id": "Izx51Ju1rXwd"
   },
   "source": [
    "## Exercise 2: create_pre_trained_model\n",
    "\n",
    "For this assignment, you will be using the pretrained model `inception V3` available on Tensorflow. In the `model` folder, you can already find the `inception V3` weights, so you can use them to initialize the [`InceptionV3`](https://www.tensorflow.org/api_docs/python/tf/keras/applications/inception_v3/InceptionV3) model."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "bfe3003f",
   "metadata": {
    "deletable": false,
    "editable": false,
    "id": "-lEzPAqxrPcU",
    "tags": [
     "graded"
    ]
   },
   "outputs": [],
   "source": [
    "# Define the path to the inception v3 weights\n",
    "LOCAL_WEIGHTS_FILE = './model/inception_v3_weights_tf_dim_ordering_tf_kernels_notop.h5'"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b0fa4ddd",
   "metadata": {
    "id": "ZPQb0PkT9_3w"
   },
   "source": [
    "Complete the `create_pre_trained_model` function below. You should specify the correct `input_shape` for the model (remember that you set a new resolution for the images instead of the native 300x300). Remember to make all of the layers non-trainable, since you will be using the weights you just downloaded."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9bce0e6f",
   "metadata": {
    "cellView": "code",
    "deletable": false,
    "id": "x2JnQ6m8r5oe",
    "tags": [
     "graded"
    ]
   },
   "outputs": [],
   "source": [
    "# GRADED FUNCTION: create_pre_trained_model\n",
    "\n",
    "def create_pre_trained_model():\n",
    "    \"\"\"Creates the pretrained inception V3 model\n",
    "\n",
    "    Returns:\n",
    "        tf.keras.Model: pre-trained model\n",
    "    \"\"\"\n",
    "\n",
    "    ### START CODE HERE ###\n",
    "    \n",
    "    pre_trained_model = tf.keras.applications.inception_v3.InceptionV3( \n",
    "        include_top=False, \n",
    "        input_shape=None,\n",
    "        weights=None\n",
    "    ) \n",
    "\n",
    "    # Make all the layers in the pre-trained model non-trainable\n",
    "    pre_trained_model.None = None\n",
    "\n",
    "    ### END CODE HERE ###\n",
    "\n",
    "    return pre_trained_model"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "bb83804c",
   "metadata": {
    "deletable": false,
    "editable": false,
    "id": "ve7eh9iztT4q",
    "tags": []
   },
   "outputs": [],
   "source": [
    "# Create the pre-trained model\n",
    "pre_trained_model = create_pre_trained_model()\n",
    "\n",
    "# Count the total number of parameters and how many are trainable\n",
    "num_total_params = pre_trained_model.count_params()\n",
    "num_trainable_params = sum([w.shape.num_elements() for w in pre_trained_model.trainable_weights])\n",
    "\n",
    "print(f\"There are {num_total_params:,} total parameters in this model.\")\n",
    "print(f\"There are {num_trainable_params:,} trainable parameters in this model.\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e076f61d",
   "metadata": {
    "id": "mRioO7FH5a8I"
   },
   "source": [
    "**Expected Output:**\n",
    "```\n",
    "There are 21,802,784 total parameters in this model.\n",
    "There are 0 trainable parameters in this model.\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6aa56339",
   "metadata": {
    "deletable": false,
    "editable": false,
    "tags": []
   },
   "outputs": [],
   "source": [
    "# Test your code!\n",
    "unittests.test_create_pre_trained_model(create_pre_trained_model)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b0cc78e1",
   "metadata": {},
   "source": [
    "Now print the summary for the `pre_trained_model`. If you scroll down to the end of your output you will see that the layers in the model are set to non-trainable, since the number of `Total params` is the same as `Non-trainable params`. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "eba8f269",
   "metadata": {
    "deletable": false,
    "editable": false,
    "scrollable": true,
    "scrolled": true,
    "tags": []
   },
   "outputs": [],
   "source": [
    "# Print the model summary\n",
    "pre_trained_model.summary()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7a7e7787",
   "metadata": {
    "id": "dFtwDyKj-4GR"
   },
   "source": [
    "## Creating callbacks for later\n",
    "You do not want your model to train more than it is necessary, so you will be creating a callback to stop the training once an accuracy of 99.9% is reached. Since you have already worked with callbacks beforehand in this specialization, this callback is provided for you, just run the cell below. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4e24ee06",
   "metadata": {
    "deletable": false,
    "editable": false,
    "id": "SeVjZD2o7gWS",
    "tags": []
   },
   "outputs": [],
   "source": [
    "# Define a Callback class that stops training once accuracy reaches 99.9%\n",
    "class EarlyStoppingCallback(tf.keras.callbacks.Callback):\n",
    "    def on_epoch_end(self, epoch, logs=None):\n",
    "        if logs['accuracy']>0.999:\n",
    "            self.model.stop_training = True\n",
    "            print(\"\\nReached 99.9% accuracy so cancelling training!\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a1468746",
   "metadata": {
    "id": "lHZnFl-5_p3a"
   },
   "source": [
    "## Exercise 3: output_of_last_layer\n",
    "Now that the pre-trained model is ready, you need to \"glue\" it to your own model to solve the task at hand. For this you will need the last output of the pre-trained model, since this will be the input for your own. Complete the `output_of_last_layer` function below.\n",
    "\n",
    "**Note:** For grading purposes use the `mixed7` layer as the last layer of the pre-trained model. However, after submitting feel free to come back here and play around with this."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ab766b8a",
   "metadata": {
    "deletable": false,
    "id": "CFsUlwdfs_wg",
    "tags": [
     "graded"
    ]
   },
   "outputs": [],
   "source": [
    "# GRADED FUNCTION: output_of_last_layer\n",
    "\n",
    "def output_of_last_layer(pre_trained_model):\n",
    "    \"\"\"Fetches the output of the last desired layer of the pre-trained model\n",
    "\n",
    "    Args:\n",
    "        pre_trained_model (tf.keras.Model): pre-trained model\n",
    "\n",
    "    Returns:\n",
    "        tf.keras.KerasTensor: last desired layer of pretrained model\n",
    "    \"\"\"\n",
    "    ### START CODE HERE ###\n",
    "\n",
    "    last_desired_layer = None\n",
    "    last_output = None\n",
    "    \n",
    "    print('last layer output shape: ', last_output.shape)\n",
    "    \n",
    "    ### END CODE HERE ###\n",
    "\n",
    "    return last_output"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3136b969",
   "metadata": {
    "id": "13AEzKG2A6_J"
   },
   "source": [
    "Check that everything works as expected:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7333bd54",
   "metadata": {
    "deletable": false,
    "editable": false,
    "id": "zOJPUtMN6PHo",
    "tags": []
   },
   "outputs": [],
   "source": [
    "last_output = output_of_last_layer(pre_trained_model)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fe6edaec",
   "metadata": {
    "id": "XqIWKZ_h7CuY"
   },
   "source": [
    "**Expected Output (if `mixed7` layer was used):**\n",
    "\n",
    "```\n",
    "last layer output shape:  (None, 7, 7, 768)\n",
    "last layer output:  KerasTensor(type_spec=TensorSpec(shape=(None, 7, 7, 768), dtype=tf.float32, name=None), name='mixed7/concat:0', description=\"created by layer 'mixed7'\")\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0906c42e",
   "metadata": {
    "deletable": false,
    "editable": false,
    "tags": []
   },
   "outputs": [],
   "source": [
    "# Test your code!\n",
    "unittests.test_output_of_last_layer(output_of_last_layer, pre_trained_model)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "40bf632f",
   "metadata": {
    "id": "0Rp-J6JuwJTq"
   },
   "source": [
    "Now you will create the final model by adding some additional layers on top of the pre-trained model.\n",
    "\n",
    "Complete the `create_final_model` function below. You will need to use Tensorflow's [Functional API](https://www.tensorflow.org/guide/keras/functional) for this since the pretrained model has been created using it. \n",
    "\n",
    "Let's double check this first:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "03059fb9",
   "metadata": {
    "deletable": false,
    "editable": false,
    "id": "cKQknB4j7K9y",
    "tags": []
   },
   "outputs": [],
   "source": [
    "# Print the type of the pre-trained model\n",
    "print(f\"The pretrained model has type: {type(pre_trained_model)}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "04f88239",
   "metadata": {
    "id": "Kt7AU7jP7LW9"
   },
   "source": [
    "## Exercise 4: create_final_model\n",
    "\n",
    "To create the final model, you will use tf.keras.Model class by defining the appropriate inputs and outputs. If you need any help doing this, you can check the official [docs](https://www.tensorflow.org/api_docs/python/tf/keras/Model).\n",
    "\n",
    "There is more than one way to implement the final layer for this kind of binary classification problem. **For this exercise, use a layer with a single unit and a sigmoid activation function along with an appropriate loss function**. This way the number of parameters to train is consistent with the expected outputs presented later.\n",
    "\n",
    "To help you build the full model, remember that you can get the input from any existing model by using its `input` attribute and by using the Funcional API you can use the last layer directly as output when creating the final model."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7da775fc",
   "metadata": {
    "cellView": "code",
    "deletable": false,
    "id": "BMXb913pbvFg",
    "tags": [
     "graded"
    ]
   },
   "outputs": [],
   "source": [
    "# GRADED FUNCTION: create_final_model\n",
    "\n",
    "def create_final_model(pre_trained_model, last_output):\n",
    "    \"\"\"Creates final model by adding layers on top of the pretrained model.\n",
    "\n",
    "    Args:\n",
    "        pre_trained_model (tf.keras.Model): pre-trained inceptionV3 model\n",
    "        last_output (tf.keras.KerasTensor): last layer of the pretrained model\n",
    "\n",
    "    Returns:\n",
    "        Tensorflow model: final model\n",
    "    \"\"\"\n",
    "    \n",
    "    # Flatten the output layer of the pretrained model to 1 dimension\n",
    "    x = tf.keras.layers.Flatten()(last_output)\n",
    "\n",
    "    ### START CODE HERE ###\n",
    "\n",
    "    # Add a fully connected layer with 1024 hidden units and ReLU activation\n",
    "    x = None\n",
    "    # Add a dropout rate of 0.2\n",
    "    x = None  \n",
    "    # Add a final sigmoid layer for classification\n",
    "    x = None        \n",
    "\n",
    "    # Create the complete model by using the Model class\n",
    "    model = tf.keras.Model(inputs=None, outputs=None)\n",
    "\n",
    "    # Compile the model\n",
    "    model.compile( \n",
    "        optimizer=tf.keras.optimizers.RMSprop(learning_rate=0.00001), \n",
    "        loss=None, # use a loss for binary classification\n",
    "        metrics=['accuracy'] \n",
    "    )\n",
    "\n",
    "    ### END CODE HERE ###\n",
    "  \n",
    "    return model"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1c44aaa5",
   "metadata": {
    "deletable": false,
    "editable": false,
    "id": "cL6ga5Z1783H",
    "tags": []
   },
   "outputs": [],
   "source": [
    "# Save your model in a variable\n",
    "model = create_final_model(pre_trained_model, last_output)\n",
    "\n",
    "# Inspect parameters\n",
    "total_params = model.count_params()\n",
    "num_trainable_params = sum([w.shape.num_elements() for w in model.trainable_weights])\n",
    "\n",
    "print(f\"There are {total_params:,} total parameters in this model.\")\n",
    "print(f\"There are {num_trainable_params:,} trainable parameters in this model.\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "aacc7459",
   "metadata": {
    "id": "J4d3zlcQDrvm"
   },
   "source": [
    "**Expected Output:**\n",
    "```\n",
    "There are 47,512,481 total parameters in this model.\n",
    "There are 38,537,217 trainable parameters in this model.\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a754a85d",
   "metadata": {
    "id": "_eqwHj5xEBZ7"
   },
   "source": [
    "Wow, that is a lot of parameters!\n",
    "\n",
    "After submitting your assignment later, try re-running this notebook but using the original resolution of 300x300, you will be surprised to see how many more parameters there are for that case."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0cd1af3d",
   "metadata": {
    "deletable": false,
    "editable": false,
    "tags": []
   },
   "outputs": [],
   "source": [
    "# Test your code!\n",
    "unittests.test_create_final_model(create_final_model, pre_trained_model, last_output)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "66c0a817",
   "metadata": {},
   "source": [
    "Before training the model, there is one small preprocessing you need to apply to the input images. According to the `inception_v3` [documentation](https://www.tensorflow.org/api_docs/python/tf/keras/applications/inception_v3/InceptionV3), the model expects you to apply [`tf.keras.applications.inception_v3.preprocess_input`](https://www.tensorflow.org/api_docs/python/tf/keras/applications/inception_v3/preprocess_input) to the images, which simply scales the input pixels between 1 and -1. Run the cell below to define a `preprocess` function, which you can then apply to the data."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8f7753a0-25a0-4327-be13-a766597ddcd3",
   "metadata": {
    "deletable": false,
    "editable": false,
    "tags": []
   },
   "outputs": [],
   "source": [
    "# Define the preprocess function\n",
    "def preprocess(image, label):\n",
    "    image = tf.keras.applications.inception_v3.preprocess_input(image)\n",
    "    return image, label\n",
    "\n",
    "# Apply the preprocessing to all datasets\n",
    "training_dataset = training_dataset.map(preprocess)\n",
    "validation_dataset = validation_dataset.map(preprocess)\n",
    "test_dataset = test_dataset.map(preprocess)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9c29c210-4e4b-481b-af8a-3d8119b9c98c",
   "metadata": {},
   "source": [
    "Now that you have defined your model and the preprocessing function, go ahead and train it. Note the `map` method used to apply the preprocessing to the train, validation and test datasets."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6f7ae7e2",
   "metadata": {
    "deletable": false,
    "editable": false,
    "id": "Blhq2MAUeyGA",
    "tags": []
   },
   "outputs": [],
   "source": [
    "# Run this and see how many epochs it takes before the callback fires\n",
    "history = model.fit(\n",
    "    training_dataset,\n",
    "    validation_data = validation_dataset,\n",
    "    epochs = 100,\n",
    "    verbose = 2,\n",
    "    callbacks = [EarlyStoppingCallback()],\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0bf936b8",
   "metadata": {
    "id": "Y94djl4t0sK5"
   },
   "source": [
    "The training should have stopped after less than 5 epochs and it should have reached an accuracy over 99,9% (firing the callback). This happened so quickly because of the pre-trained model you used, which already contained information to classify humans from horses. Really cool!\n",
    "\n",
    "Now take a quick look at the training and validation accuracies for each epoch of training. Of course, since the training was done so fast you will not have many points to visualize."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7efcb073",
   "metadata": {
    "deletable": false,
    "editable": false,
    "id": "C2Fp6Se9rKuL",
    "tags": []
   },
   "outputs": [],
   "source": [
    "# Plot the training and validation accuracies for each epoch\n",
    "\n",
    "acc = history.history['accuracy']\n",
    "val_acc = history.history['val_accuracy']\n",
    "loss = history.history['loss']\n",
    "val_loss = history.history['val_loss']\n",
    "\n",
    "epochs = range(len(acc))\n",
    "\n",
    "plt.plot(epochs, acc, 'r', label='Training accuracy')\n",
    "plt.plot(epochs, val_acc, 'b', label='Validation accuracy')\n",
    "plt.title('Training and validation accuracy')\n",
    "plt.legend(loc=0)\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f956066d-7500-4a1c-8888-e4758b068f48",
   "metadata": {},
   "source": [
    "## Testing your model\n",
    "Now that you have trained your full model, you can go ahead and test the performance on the test data you created earlier. You can simply use the `.evaluate` method for this purpose:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "566a8cc1-1c6b-4348-8246-a7c9d7e0dcb5",
   "metadata": {
    "deletable": false,
    "editable": false,
    "tags": []
   },
   "outputs": [],
   "source": [
    "test_loss, test_accuracy = model.evaluate(test_dataset)\n",
    "print(f'Test loss: {test_loss},\\nTest accuracy: {test_accuracy}')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8b711efd",
   "metadata": {
    "id": "7w54-pbB1W9r"
   },
   "source": [
    "**Congratulations on finishing this week's assignment!**\n",
    "\n",
    "You have successfully implemented a convolutional neural network that leverages a pre-trained network to help you solve the problem of classifying humans from horses.\n",
    "\n",
    "**Keep it up!**"
   ]
  }
 ],
 "metadata": {
  "accelerator": "GPU",
  "grader_version": "2",
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.1.undefined"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
